#include <QtCore>
#include "downloadmgr.h"

Download::Download(Episode& e, QObject* parent)
    : QObject(parent), episode(e), totalBytes(0), bytesWritten(0), progress(0), status(NotStarted) {
    QString fn = e.downloadPath();
    file.setFileName(fn);
    if(file.exists()) {
        bytesWritten = file.size();
//        qDebug() << "Found partial download for " << e.name << " of size: " << bytesWritten;
    }
}

Download::~Download() {
    file.close();
}

void Download::abort() {
    file.close();
    status = Aborted;
    if (!reply.isNull()) {
        reply->abort();
        reply->deleteLater();
    }
    qDebug() << "Download Aborted: " << episode.name;
}

void Download::start(QNetworkReply *r) {
    if (!reply.isNull())
        reply->deleteLater();
    reply=r;
    status = Downloading;
}



DownloadMgr::DownloadMgr(QObject *parent) : QObject(parent) {

    connect (&m_manager, SIGNAL(finished(QNetworkReply*)),
             this, SLOT(downloadEpisodeFinished(QNetworkReply*)), Qt::UniqueConnection);

}

Download* DownloadMgr::start(Episode& ep) {
    QNetworkRequest request(ep.downloadLink);


    Download* d = 0;
    if (m_downloads.contains(ep)) {
        d = m_downloads[ep];
    }
    else {
        d = new Download(ep, this);
        m_downloads[ep]=d;
    }
    if (d->status != Download::Downloading) {
        QNetworkReply* reply =  m_manager.get(request);
        reply->setReadBufferSize(2 * BufferSize);
        d->start(reply);
        m_replyHash[reply] = d;
        connect (reply, SIGNAL(downloadProgress(qint64,qint64)),
            this, SLOT(updateProgress(qint64, qint64)));
    }
    // qDebug() << "Get: " << request.url();
    return d;
}

void DownloadMgr::abort(Episode &e) {
    Download* d = m_downloads[e];
    if (d == 0) {
        qDebug() << e.name << "Can't find current download to abort!!";
        return;
    }
    d->abort();

    emit updateViews(e);
}

Download* DownloadMgr::getDownload(const Episode& e) const {
    if (m_downloads.contains(e)) return m_downloads[e];
    else return 0;
}

void DownloadMgr::remove(Episode& e) {
    Download* d = m_downloads[e];
    if (d != 0) d->deleteLater();
    m_downloads.remove(e);
    //m_replyHash.remove(d->reply);

}

void DownloadMgr::updateProgress(qint64 received, qint64 total) {
    // This has the side-effect of ignoring redirect packets which we do not want to touch here.
    if (total < BufferSize) return;

    QNetworkReply *reply = qobject_cast<QNetworkReply*>(sender());
    Q_ASSERT(reply != 0);
    Download* d = m_replyHash[reply];

    if (d->bytesWritten >= total) { 
        // we already have the episode.
        d->abort();
        d->status = Download::Complete;
        d->progress = 100;
        emit updateViews(d->episode);
        return;
    }
    d->progress = 100 * d->bytesWritten / total;

    if (received < BufferSize) return;
    if (received < d->bytesWritten) {
        int bytes = d->reply->bytesAvailable();
        int newBytes = received + bytes - d->bytesWritten;
        if (newBytes > 0) {
            bytes = bytes - newBytes;
        }
        d->reply->read(bytes);
        // qDebug() << " Skipping " << bytes << ".  received: " << received << " bytesWritten: " << d->bytesWritten;
        return;
    }
    if (savePartial(d)) {
        d->totalBytes = total;
        emit updateViews(d->episode);
    }
}

bool DownloadMgr::savePartial(Download* d) {
    int bytesAvailable = d->reply->bytesAvailable();

    if (bytesAvailable < BufferSize) return false;
    QByteArray ba = d->reply->read(BufferSize);
    if (!d->file.isOpen()) {
        if (!d->file.open(QIODevice::Append)) {
            qDebug() << "Unable to open for append: " << d->episode.downloadPath();
            return false;
        }
    }
    d->file.write(ba);
    d->bytesWritten += ba.size();
    d->status = Download::Downloading;
    return true;
}



void DownloadMgr::downloadEpisodeFinished(QNetworkReply * reply) {
//    qDebug() << reply->rawHeaderList();


    Download* d = m_replyHash[reply];
    if (d->status == Download::Aborted) return;
    Episode& e = d->episode;

    qApp->processEvents();
    m_replyHash.remove(reply);

    QUrl url = reply->url();
    reply->deleteLater();

    QVariant redirectVar = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
    if (redirectVar != QVariant()) {
        QUrl url = redirectVar.toUrl();
        if (e.downloadLink == url) {
            qDebug() << "Redirect loop.";
            return;
        }
        e.downloadLink = url;
        d->progress = 1;
        d->status = Download::Downloading;
        d->bytesWritten = 0;
        qDebug() << "REDIRECT:" << url.toString();
        emit updateViews(e);
        start(e);
        return;
    }
    if (reply->error()) {
        d->abort();
        qDebug() << QString("Download of %1 failed: %2\n")
                .arg(url.toEncoded().constData())
                .arg(qPrintable(reply->errorString()));
        emit updateViews(e);
        return;
    }

    QByteArray contentType = reply->rawHeader("Content-Type");
    qDebug() << "Content Type: " << contentType;    
    QByteArray bytes = reply->readAll();
    qDebug() << bytes.size() + d->bytesWritten << "bytes read. ";

    QString contentStr(contentType);
    if (contentStr.startsWith("text/html")) {
        QRegExp redirectURLPattern = QRegExp("\"(http://[^\"]+\\.mp3)\"");
        // search 'bytes' for a http://*.\.mp3 url
        contentStr = QString(bytes);
        int idx = redirectURLPattern.indexIn(contentStr);
        if (idx > -1) {
              QString capturedURL = redirectURLPattern.cap(1);
              QUrl url = QUrl::fromUserInput(capturedURL);
              e.downloadLink = url;
              d->progress = 1;
              d->status = Download::Downloading;
              d->bytesWritten = 0;
              qDebug() << "REDIRECT from HTML content:" << url.toString();
              emit updateViews(e);
              start(e);
              return;
        }
    }

    if (!d->file.isOpen()) {
        if (!d->file.open(QIODevice::Append)) {
            qDebug() << "Unable to open for append: " << d->file.fileName();
            d->status = Download::Aborted;
            emit updateViews(e);
            return;
        }
    }
    d->file.write(bytes);
    d->bytesWritten += bytes.size();
    d->file.close();
    d->status = Download::Complete;
    d->progress = 100;
    emit updateViews(e);
}


